package ngat.oss;

import ngat.util.*;
import ngat.util.logging.*;
import ngat.net.transaction.*;
import ngat.message.base.*;
import ngat.message.OSS.*;
import ngat.net.*;

import java.lang.reflect.*;
import java.util.*;

/** Checks the transaction store and executes any transactions found.*/
public class QueryThread extends ControlThread {
    
    public static final long DEFAULT_QUERY_POLLING_INTERVAL = 500L;

 
    Vector queues;

    /** Transaction counter.*/
    volatile int count = 0;

    /** Check counter.*/
    volatile int cc = 0;

    /** Trace logger.*/
    Logger trace = LogManager.getLogger("TRACE");

    /** Create a QueryThread latched to Queues.
     */
    public QueryThread(Vector queues) {
	    super("QueryThread", true);
	    this.queues = queues;
    }


    /** Initialise the execution thread.*/
    public void initialise() {}
    
    /** Keep checking for Transactions and execute.*/
    public void mainTask() {
	
	try {sleep(DEFAULT_QUERY_POLLING_INTERVAL);} catch (InterruptedException ix) {}
	cc++;

	if (cc % 100 == 0) {
	    trace.log(1, "QT: starting Q cycle: "+cc);
	    for (int iq = 0; iq < queues.size(); iq++) {
	    
		List qq = (List)queues.get(iq);
		if (qq.isEmpty()) 
		    trace.log(1, "QT: Q_Size["+iq+"] = EMPTY");
		else
		    trace.log(1, "QT: Q_Size["+iq+"] = "+qq.size());
	    }
	}

	// cycle down the queue list from q0;
	for (int iq = 0; iq < queues.size(); iq++) {
	    
	    List qq = (List)queues.get(iq);
	    
	    if (qq.isEmpty()) {
		//trace.log(1, "QT: Q["+iq+"]");
	    } else {
		// watch this it may be out anyway????
		trace.log(1, "QT: Checking Q["+iq+"] Containing "+qq.size()+" transactions");
		TransactionUnit tu = (TransactionUnit)qq.remove(0);
		if (tu != null) {
		    count++;
		    TRANSACTION transaction = tu.getTransaction();
		    trace.log(1, "QT: Extracted transaction from Q["+iq+"]");
		    if (transaction != null) {
			trace.log(1, "QT: Preparing to execute transaction...");
			TRANSACTION_DONE done = executeTransaction(transaction);
			trace.log(1, "QT: Transaction completed");
			tu.setDone(done);
			tu.setCompleted(true);
		    }
		}
	    }
	} // next lower priority queue
    }
    
    /** Stop the execution thread.*/
    public void shutdown() {}

    
    /** Execute the transaction request.
     * @param trans The TRANSACTION request to execute.
     * @param cd Client's authorization.
     */
    private TRANSACTION_DONE executeTransaction(TRANSACTION transaction) {
	
	// Check authorization.
	ClientDescriptor cd = transaction.getClientDescriptor();
	if (cd == null)
	    cd = new ClientDescriptor("default-client", 
				      ClientDescriptor.RCS_CLIENT, 
				      ClientDescriptor.RCS_PRIORITY, 
				      "default-password", 
				      12345L);
	if (cd == null)
	    return failed(563, "No authorization specified", null);
	
	trace.log(3, "ClientDescriptor: "+cd);
	
	String userPriority = ClientDescriptor.toClientAuthorizationString(cd.getPriority());
	
	trace.log(3, "User permission level is: ["+userPriority+"]");
	
	Properties authorization = ServerContext.getAuthorizationTable();
	
	String perm = userPriority+"."+transaction.getTransIdent();
	// e.g. COI_USER.GET_PROPOSAL or PEST.REMOVE_PROPOSAL or ROBOTIC1.PAUSE_SERVER
	
	if (authorization.getProperty(perm) == null) 
	    return failed(555, "Invalid permission level ("+userPriority+") for "+transaction.getTransIdent(),	null);
		
	// OK authorized.
	TRANSACTION_DONE response = null;
	
	// Get the Impl-version class name. ALWAYS same as Trans plus 'Impl'
	String transName = transaction.getClass().getName();
	int k = transName.lastIndexOf(".");
	String implName = "ngat.oss."+transName.substring(k+1)+"Impl"; 
	trace.log(1, "Implementor name:"+implName);

	TransactionImpl transImpl = null;
	// Get the TRANS and Impl Class objects.
	try {
	    Class transClass = Class.forName(transName);
	    Class implClass  = Class.forName(implName);
	    // Make a constuctor for the Impl Class.
	    Constructor implCon = implClass.getConstructor(new Class[]{transClass});
	    // Make an Instance of the P Class.
	    transImpl = (TransactionImpl)implCon.newInstance(new Object[]{transaction});
	    trace.log(1, "Ready implementation: "+(transImpl != null ? transImpl.getClass().getName() : " NO Implementor was created !"));
	} catch (Exception ex) {
	    trace.log(1, "Error: Constructing implementor: Expected Class: "+implName+", Exception: "+ex);	
	    System.err.println("Error building transaction implementor");
	    ex.printStackTrace();
	    return failed(666, "Unable to construct implementor", ex);
	}
	
	// Catch any and all Exceptions thrown by dodgy transactions. 
	// The TSC(TransImpl) must NOT be allowed to crash whatever happens in a TR.!
	try {
	    if (transImpl != null) {
		trace.log(1, "Starting Transaction: " + transaction.getTransIdent());
		response = transImpl.exec(); // carry out the transaction here
		
		trace.log(1, "Completed Transaction: " + transaction.getTransIdent()+
			  "\n"+ transaction.getClass().getName());
		if (response == null) {
		    trace.log(1, "Transaction implementor result was NULL");		   		   
		    return failed(TRANSACTION.TRANSACTION_FAILURE,
				  "No Transaction response was generated",
				  null);
		} else {
		    trace.log(1, "Transaction result type: " + response.getClass().getName());
		}
	    } else {
		trace.log(1, "TransactionImpl could not be generated");		
		return failed(TRANSACTION.TRANSACTION_FAILURE,
			      "TransactionImpl could not be generated",
			      null);
	    }
	} catch (Exception ee) {
	    trace.log(1, "Transaction execution failure: " + 
		      "\nException: "+ee);
	    ServerContext.dumpStack(ee);
	    ee.printStackTrace();
	    
	    return failed(TRANSACTION.TRANSACTION_FAILURE,
			  "Transaction execution error",
			  ee);
	}
	
	trace.log(1, "Transaction result was: "+response);
	
	response.setId(transaction.getId());
	response.setClientDescriptor(new ClientDescriptor(getName(),0,0));					
	response.setCrypto(new Crypto("/OSS"));
	
	return response;
	
    }
    
    /** Returns a failure message with specified parameters.
     * @param errorNum    The error code (see ngat.message.OSS.TRANSACTION).
     * @param errorString An explanative message.
     * @param ex          An optional Exception which triggerred the failure.
     */
    private TRANSACTION_DONE failed(int errorNum, String errorString, Exception ex) {
	TRANSACTION_DONE response = new TRANSACTION_DONE("");
	response.setErrorNum(errorNum);
	response.setErrorString(errorString+" : "+ex);
	//response.setException(ex);
	return response;
    }
        
}

